#include "AEApplication.hpp"

using namespace OgreOggSound;

using namespace arete::gui;
using namespace arete::io;
using namespace arete::thread;
using namespace arete::components;
using namespace arete::input;
using namespace arete::rendering;
using namespace arete::scripting;
using namespace arete::audio;

BEGIN_ARETE_NAMESPACE(core)

	AEApplication::AEApplication(void) :
	m_exiting(false),
	m_renderingengine(NULL),
	m_statemanager(NULL),
	m_settings(NULL),
	m_taskmanager(NULL),
	m_scriptengine(NULL),
	m_logmanager(NULL),
	m_guiengine(NULL),
	m_inputmanager(NULL),
	m_audiomanager(NULL)
{
}

AEApplication::~AEApplication(void)
{
	m_updating.clear();

	AELogManager::log(ELL_NONE,"*****************************************");
	AELogManager::log(ELL_NONE,"*****************************************");
	AELogManager::log(ELL_NONE,"*** ---   Shutting down engine    --- ***");
	AELogManager::log(ELL_NONE,"*****************************************");

	m_settings.reset(); m_settings = NULL;
	m_statemanager.reset(); m_statemanager = NULL;
	m_taskmanager.reset(); m_taskmanager = NULL;
	m_inputmanager.reset(); m_inputmanager = NULL;
	m_scriptengine.reset(); m_scriptengine = NULL;
	m_guiengine.reset(); m_guiengine = NULL;
	m_renderingengine.reset(); m_renderingengine = NULL;
	m_logmanager.reset(); m_logmanager = NULL;
	m_audiomanager.reset(); m_audiomanager = NULL;
}

bool AEApplication::initialize(char** _args,int _size)
{
	m_renderingengine = std::make_shared<AERenderingEngine>();
	m_statemanager = std::make_shared<AEStateManager>();
	m_settings = std::make_shared<AESettingsManager>();
	m_taskmanager = std::make_shared<AETaskManager>();
	m_scriptengine = std::make_shared<AEScriptEngine>();
	m_logmanager = std::make_shared<AELogManager>();
	m_guiengine = std::make_shared<AEGUISystem>();
	m_inputmanager = std::make_shared<AEInputManager>();
	m_audiomanager = std::make_shared<AEAudioManager>();

	try
	{
		string userDir = findUserDirectory();

		m_logmanager->initialize(userDir);

		AELogManager::log(ELL_NONE,"*****************************************");
		AELogManager::log(ELL_NONE,"*** --- " VER_PRODUCTNAME_STR " " VER_PRODUCT_VERSION_STR);
		parseArguements(_args,_size);

		m_settings->initialize(userDir,"settings.cfg");
		m_settings->loadSettings();

		m_taskmanager->initialize(m_settings->getSettingAsInt("Application","Cores",1));

		m_scriptengine->initialize();
		m_renderingengine->initialize();
		m_statemanager->initialize();

		initializeAudio();

		m_inputmanager->initialize(m_renderingengine->getWindow());

		registerForUpdates(m_scriptengine.get(), m_scriptengine->getGCInterval());
		registerForUpdates(m_inputmanager.get(), 60.0f);

		registerScriptGlobals();

		AELogManager::log(ELL_NONE,"*****************************************");
	}
	catch(...) { return false; }

	return true;
}

void AEApplication::parseArguements(char** _args,int _size)
{
	AELogManager::log(ELL_NONE,"*** --- Command Line Options");

	for(int i = 1; i < _size; i++)
	{
		if(std::strcmp(_args[i],"+verbose") == 0)
		{
			m_settings->setSetting("Application","LogLevel", std::to_string(Ogre::LL_NORMAL));
			AELogManager::log(ELL_NONE,"*** --- \tVerbose");
			m_logmanager->setLogDetail(Ogre::LL_NORMAL);
		}
		else if(std::strcmp(_args[i],"+debug") == 0)
		{
			m_settings->setSetting("Application", "LogLevel", std::to_string(Ogre::LL_BOREME));
			AELogManager::log(ELL_NONE,"*** --- \tDebug");
			m_logmanager->setLogDetail(Ogre::LL_BOREME);
		}
		else if(std::strcmp(_args[i],"+developer") == 0)
		{
			m_settings->setSetting("Application", "Developer", "true");
			AELogManager::log(ELL_NONE,"*** --- \tDeveloper");
		}
	}
}

void AEApplication::run(void)
{
	m_exiting = false;
	long lTimeLastRenderUpdate = 0, lTimeLastInputUpdate = 0;
	long lTimeLastLogicUpdate = 0, lTimeLastSoundUpdate = 0;
	m_renderingengine->getTimer()->reset();

	while(!m_renderingengine->getWindow()->isClosed() && !m_exiting)
	{
		Ogre::WindowEventUtilities::messagePump();

		if(m_renderingengine->getWindow()->isActive())
		{
			Ogre::map<AEUpdateable*,std::pair<long,float>>::iterator iter;
			for(iter = m_updating.begin(); iter != m_updating.end(); ++iter)
			{
				const long lTimeCurrentUpdatableUpdate = m_renderingengine->getTimer()->getMilliseconds();
				const float fTimeSinceLastUpdatableUpdate = (lTimeCurrentUpdatableUpdate - iter->second.first) / 1000.0f;

				if(fTimeSinceLastUpdatableUpdate >= 1.0f / iter->second.second) {
					iter->first->update(fTimeSinceLastUpdatableUpdate);
					iter->second.first = lTimeCurrentUpdatableUpdate;
				}
			}

			const long lTimeCurrentLogicUpdate = m_renderingengine->getTimer()->getMilliseconds();
			const float fTimeSinceLastLogicUpdate = (lTimeCurrentLogicUpdate - lTimeLastLogicUpdate) / 1000.0f;
			if(m_statemanager->getActiveState())
			{
				m_statemanager->getActiveState()->update(fTimeSinceLastLogicUpdate);
				lTimeLastLogicUpdate = lTimeCurrentLogicUpdate;
			}

			const long lTimeCurrentSoundUpdate = m_renderingengine->getTimer()->getMicroseconds();
			const float fTimeSinceLastSoundUpdate = (lTimeCurrentSoundUpdate - lTimeLastSoundUpdate) / 1000.0f;
			if(fTimeSinceLastSoundUpdate >= 1.0f / 60.0f)
			{
				OgreOggSound::OgreOggSoundManager::getSingleton().update(fTimeSinceLastSoundUpdate);
				lTimeLastSoundUpdate = lTimeCurrentSoundUpdate;
			}

			const long lTimeCurrentRenderUpdate = m_renderingengine->getTimer()->getMilliseconds();
			const float fTimeSinceLastRenderUpdate = (lTimeCurrentRenderUpdate - lTimeLastRenderUpdate) / 1000.0f;
			if(fTimeSinceLastRenderUpdate >= 1.0f / m_settings->getSettingAsFloat("Graphics","Target FPS",60.0f))
			{
				if(!m_renderingengine->getRoot()->renderOneFrame(fTimeSinceLastRenderUpdate))
					m_exiting = true;

				lTimeLastRenderUpdate = lTimeCurrentRenderUpdate;
			}
		}
		else
		{
			Sleep(1000);
		}
	}
}

void AEApplication::registerForUpdates(AEUpdateable *_module,float _freq)
{
	m_updating[_module] = std::pair<long,float>(0,_freq);
}

void AEApplication::deregisterForUpdates(AEUpdateable *_module)
{
	Ogre::map<AEUpdateable*,std::pair<long,float>>::iterator ele = m_updating.find(_module);
	if(ele != m_updating.end())
		m_updating.erase(ele);
}

string AEApplication::findUserDirectory(void)
{
	char documentsPath[250];
	SHGetFolderPathA(NULL,CSIDL_PERSONAL,NULL,0,documentsPath);

	string userDir = documentsPath;
	replace(userDir.begin(),userDir.end(),'\\','/');
	userDir.append("/My Games/" VER_PRODUCTNAME_STR "/");

	return userDir;
}

void AEApplication::initializeAudio( void )
{
	OgreOggSoundManager::getSingleton().init("",100U,100U,m_renderingengine->getSceneManager());
	OgreOggSoundManager::getSingleton().setMasterVolume(AESettingsManager::getSingleton().getSettingAsFloat("Sound","Master",1.0f));
}

void AEApplication::registerScriptGlobals( void )
{
	m_logmanager->registerScriptLoggers(m_scriptengine->getScriptEngine());
}

END_ARETE_NAMESPACE